package com.resource.config;

import java.io.IOException;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;

import com.sinoframework.web.servlet.bean.Response;
import com.sinoframework.web.servlet.bean.ResponseResult;
import com.sinoframework.web.servlet.security.HttpServletResponseHttpOutputMessage;

import cn.hutool.crypto.SecureUtil;


@Configuration
public class SpringSecutityBeanConfiguration {
	
    /**
     * redis工厂，默认使用lettue
     */
    @Autowired
    public RedisConnectionFactory redisConnectionFactory;
	
	/**
	 * 在spring5.0之后，springsecurity client密码 和 user密码必须用 PasswordEncoder 加密
	 * @return
	 */
    @Bean
    public BCryptPasswordEncoder bcryptPasswordEncoder(){
    	return new BCryptPasswordEncoder();
    }
    
    
    public TokenStore redisTokenStore() {
        //使用redis存储token
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        //设置redis token存储中的前缀
        redisTokenStore.setPrefix("auth-token:");
        return redisTokenStore;
    }
    
//    @Bean("jwtTokenStore")
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey(SecureUtil.md5("test_key"));//配置JWT使用的秘钥
        return accessTokenConverter;
    }
    
    
    
    
    
    
    
    
    /**
     * 	资源服务器TokenService
     * 
     * DefaultTokenServices 实现了 AuthorizationServerTokenServices, ResourceServerTokenServices接口
     * 			AuthorizationServerTokenServices：授权服务器使用
     * 			ResourceServerTokenServices：资源服务器使用【是用于向远程认证服务器验证token，同时获取token对应的用户的信息】
     * 			
     * 			DefaultTokenServices和RemoteTokenServices实现了 ResourceServerTokenServices接口
     * 							
     * @return
     */
    @Bean
    @Primary
    public ResourceServerTokenServices tokenService() {
        return jwtTokenServices();
    }
    
    
    /*************以下3个3选一*******************************************************/
    public ResourceServerTokenServices jwtTokenServices() {
    	DefaultTokenServices tokenServices = new DefaultTokenServices();
        //配置token存储
        tokenServices.setTokenStore(jwtTokenStore());
        //开启支持refresh_token，此处如果之前没有配置，启动服务后再配置重启服务，可能会导致不返回token的问题，解决方式：清除redis对应token存储
        tokenServices.setSupportRefreshToken(true);
        //复用refresh_token
        tokenServices.setReuseRefreshToken(true);
        //token有效期，设置12小时
        tokenServices.setAccessTokenValiditySeconds(12 * 60 * 60);
        //refresh_token有效期，设置一周
        tokenServices.setRefreshTokenValiditySeconds(7 * 24 * 60 * 60);
        
        return tokenServices;
    }
    
    
    //redisTokenServices 第二方资源服务器
    public ResourceServerTokenServices redisTokenServices() {
    	DefaultTokenServices tokenServices = new DefaultTokenServices();
        //配置token存储
        tokenServices.setTokenStore(redisTokenStore());
        //开启支持refresh_token，此处如果之前没有配置，启动服务后再配置重启服务，可能会导致不返回token的问题，解决方式：清除redis对应token存储
        tokenServices.setSupportRefreshToken(true);
        //复用refresh_token
        tokenServices.setReuseRefreshToken(true);
        //token有效期，设置12小时
        tokenServices.setAccessTokenValiditySeconds(12 * 60 * 60);
        //refresh_token有效期，设置一周
        tokenServices.setRefreshTokenValiditySeconds(7 * 24 * 60 * 60);
        
        return tokenServices;
    }
    
    //RemoteTokenServices 用于第三方应用【向远程认证服务器验证token，同时获取token对应的用户的信息】 
    public ResourceServerTokenServices remoteTokenServices() {
		RemoteTokenServices tokenService = new RemoteTokenServices();
		tokenService.setCheckTokenEndpointUrl("http://localhost:8888/oauth/check_token");
		tokenService.setClientId("client");
		tokenService.setClientSecret("secret");
		return tokenService;
    }
    
    /********************************************************************/
    
    
    
    
    /**
     * 
     * 	自定义【认证】异常处理 AuthenticationException 
     * @param messageConverters
     * @return
     */
    @Bean
    public AuthenticationEntryPoint customAuthenticationEntryPoint(@Autowired List<HttpMessageConverter<?>> messageConverters) {
    	
    	return new AuthenticationEntryPoint() {
    		
			@SuppressWarnings({ "unchecked", "rawtypes" })
			@Override
			public void commence(HttpServletRequest request, HttpServletResponse response,
					AuthenticationException authException) throws IOException, ServletException {
				
				ResponseResult<String>  res = Response.makeAuthorFail(authException.getMessage());
				HttpHeaders httpHeaders = new HttpHeaders();
				httpHeaders.setContentType(MediaType.APPLICATION_JSON);
				
				for (HttpMessageConverter messageConverter : messageConverters) {
					if (messageConverter.canWrite(res.getClass(), MediaType.APPLICATION_JSON)) {
						messageConverter.write(res, 
								MediaType.APPLICATION_JSON, 
								new HttpServletResponseHttpOutputMessage(httpHeaders,response));
						
						return;
					}
				}
				
				
			}
    		
    	};
    }
    
    
    /**
     * 
     * 	自定义【鉴权】异常处理 AccessDeniedException
     * @param messageConverters
     * @return
     */
    @Bean
    public AccessDeniedHandler customAccessDeniedHandler(@Autowired List<HttpMessageConverter<?>> messageConverters) {
    	
    	return new AccessDeniedHandler() {
    		
    		@SuppressWarnings({ "unchecked", "rawtypes" })
			@Override
			public void handle(HttpServletRequest request, HttpServletResponse response,
					AccessDeniedException accessDeniedException) throws IOException, ServletException {
    			accessDeniedException.printStackTrace();
				ResponseResult<String>  res = Response.makeAccessDenied(accessDeniedException.getMessage());
				HttpHeaders httpHeaders = new HttpHeaders();
				httpHeaders.setContentType(MediaType.APPLICATION_JSON);
				
				for (HttpMessageConverter messageConverter : messageConverters) {
					if (messageConverter.canWrite(res.getClass(), MediaType.APPLICATION_JSON)) {
						messageConverter.write(res, 
								MediaType.APPLICATION_JSON, 
								new HttpServletResponseHttpOutputMessage(httpHeaders,response));
						
						return;
					}
				}
				
			}
    		
    	};
    }
    
    
}
